package controller;

import java.util.ArrayList;
import java.util.PriorityQueue;

import model.AIDataNode;

/**
 * Implementation of the KNN-Algirithm
 * @author zyner
 *
 */
public class kNearestNEighbor {
	private ArrayList<AIDataNode> trainingSet;
	private ArrayList<AIDataNode> testSet;
	private int k;
	
	
	/**
	 * Constructor of the Class
	 * @param trainingSet Set for the trainingData
	 * @param testSet set for the testingData
	 */
	public kNearestNEighbor(ArrayList<AIDataNode> trainingSet,ArrayList<AIDataNode> testSet, int k){
		this.trainingSet=trainingSet;
		this.testSet=testSet;
		this.k =k;
	}
	
	/**
	 * Method to test the trained data
	 * @return [0] correct [1] wrong 
	 */
	public int [] test(){
		int numCorrect = 0;
		int numWrong = 0;
		//go through the test set,
		//calculate the distance to each node in test set
		//sort distance list
		//trim to size k
		//take majority class
		
		for (AIDataNode testNode : this.testSet){
			//Use a priority queue to find the k nearest neighbours (pop k off of queue as it is sorted)
			//priority queue used as it is faster than a sortedList
			PriorityQueue<nearestListNode> nearestList = new PriorityQueue<nearestListNode>();
			for (AIDataNode trainingNode : this.trainingSet){
				//Compute distance and add to queue
				nearestListNode nearestNode = new nearestListNode(getDistance(testNode,trainingNode),trainingNode);
				nearestList.add(nearestNode);
			}
			//Add k values to node's proximity list - the priority queue means that the list is already sorted
			for (int i = 0; i < k; i++){
				nearestListNode closest = nearestList.poll();
				testNode.addNearestNeighbor(closest.getNode());
			}
			double classCount = 0;
			//Compute majority class
			for (AIDataNode listNode : testNode.getNearestNeighborRef()){
				classCount+= listNode.getKlass();
			}
			classCount /= k;
			
			//compare to true value
			if (classCount > 0.5){ // there was a majority of class 1
				if (testNode.getKlass()==1){
					numCorrect++;
				} else {
					numWrong++;
				}
			} else {
				if (testNode.getKlass()==0){
					numCorrect++;
				} else {
					numWrong++;
				}
			}
				
		}
		int returndata[] = {numCorrect,numWrong};
		return returndata;
	}
	
	/**
	 * Calculates the distance between two nodes
	 * @param first first Node
	 * @param second Second node
	 * @return Distance as a double var
	 */
	private double getDistance(AIDataNode first,AIDataNode second){
		double returnVal = 0;
		for (int j = 0; j < first.getNodeWeight().length; j++){
			returnVal += Math.pow((first.getNodeWeight()[j] - second.getNodeWeight()[j]),2);		
		}
		return Math.sqrt(returnVal);
	}
	
	/**
	 * Class for the List of nearest nodes
	 * @author zyner
	 *
	 */
	private class nearestListNode implements Comparable<nearestListNode>{
		double distance;
		AIDataNode node;
		public nearestListNode(double distance, AIDataNode node){
			this.distance = distance;
			this.node = node;
		}
		public AIDataNode getNode(){
			return this.node;
		}
		public double getDistance(){
			return this.distance;
		}
		public int compareTo(nearestListNode node){
			if (this.distance < node.getDistance()){ 
				return -1;
			} if (this.distance > node.getDistance()){
				return 1;
			} else return 0;
		}
	}
	
}
